/**
\addtogroup EL9800_HW EL9800 Platform (Serial ESC Access)
@{
*/

/**
\file    el9800hw.c
\author EthercatSSC@beckhoff.com
\brief Implementation
Hardware access implementation for EL9800 onboard PIC18/PIC24 connected via SPI to ESC

\version 5.11

<br>Changes to version V5.10:<br>
V5.11 ECAT10: change PROTO handling to prevent compiler errors<br>
V5.11 EL9800 2: change PDI access test to 32Bit ESC access and reset AL Event mask after test even if AL Event is not enabled<br>
<br>Changes to version V5.01:<br>
V5.10 ESC5: Add missing swapping<br>
V5.10 HW3: Sync1 Isr added<br>
V5.10 HW4: Add volatile directive for direct ESC DWORD/WORD/BYTE access<br>
           Add missing swapping in mcihw.c<br>
           Add "volatile" directive vor dummy variables in enable and disable SyncManger functions<br>
           Add missing swapping in EL9800hw files<br>
<br>Changes to version V5.0:<br>
V5.01 HW1: Invalid ESC access function was used<br>
<br>Changes to version V4.40:<br>
V5.0 ESC4: Save SM disable/Enable. Operation may be pending due to frame handling.<br>
<br>Changes to version V4.30:<br>
V4.40 : File renamed from spihw.c to el9800hw.c<br>
<br>Changes to version V4.20:<br>
V4.30 ESM: if mailbox Syncmanger is disabled and bMbxRunning is true the SyncManger settings need to be revalidate<br>
V4.30 EL9800: EL9800_x hardware initialization is moved to el9800.c<br>
V4.30 SYNC: change synchronisation control function. Add usage of 0x1C32:12 [SM missed counter].<br>
Calculate bus cycle time (0x1C32:02 ; 0x1C33:02) CalcSMCycleTime()<br>
V4.30 PDO: rename PDO specific functions (COE_xxMapping -> PDO_xxMapping and COE_Application -> ECAT_Application)<br>
V4.30 ESC: change requested address in GetInterruptRegister() to prevent acknowledge events.<br>
(e.g. reading an SM config register acknowledge SM change event)<br>
GENERIC: renamed several variables to identify used SPI if multiple interfaces are available<br>
V4.20 MBX 1: Add Mailbox queue support<br>
V4.20 SPI 1: include SPI RxBuffer dummy read<br>
V4.20 DC 1: Add Sync0 Handling<br>
V4.20 PIC24: Add EL9800_4 (PIC24) required source code<br>
V4.08 ECAT 3: The AlStatusCode is changed as parameter of the function AL_ControlInd<br>
<br>Changes to version V4.02:<br>
V4.03 SPI 1: In ISR_GetInterruptRegister the NOP-command should be used.<br>
<br>Changes to version V4.01:<br>
V4.02 SPI 1: In HW_OutputMapping the variable u16OldTimer shall not be set,<br>
             otherwise the watchdog might exceed too early.<br>
<br>Changes to version V4.00:<br>
V4.01 SPI 1: DI and DO were changed (DI is now an input for the uC, DO is now an output for the uC)<br>
V4.01 SPI 2: The SPI has to operate with Late-Sample = FALSE on the Eva-Board<br>
<br>Changes to version V3.20:<br>
V4.00 ECAT 1: The handling of the Sync Manager Parameter was included according to<br>
              the EtherCAT Guidelines and Protocol Enhancements Specification<br>
V4.00 APPL 1: The watchdog checking should be done by a microcontroller<br>
                 timer because the watchdog trigger of the ESC will be reset too<br>
                 if only a part of the sync manager data is written<br>
V4.00 APPL 4: The EEPROM access through the ESC is added

*/


/*--------------------------------------------------------------------------------------
------
------    Includes
------
--------------------------------------------------------------------------------------*/
#include "ecat_def.h"

#include "ecatslv.h"

#define    _LAN9252HW_ 1
#include <ethercat_lan9252/ethercat_lan9252_hw.h>
#undef    _LAN9252HW_
/* ECATCHANGE_START(V5.11) ECAT10*/
/*remove definition of _EL9800HW_ (#ifdef is used in el9800hw.h)*/
/* ECATCHANGE_END(V5.11) ECAT10*/

#include "ecatappl.h"

#include "bsp/bsp.h"
/*--------------------------------------------------------------------------------------
------
------    internal Types and Defines
------
--------------------------------------------------------------------------------------*/

typedef union
{
    unsigned short    Word;
    unsigned char    Byte[2];
} UBYTETOWORD;

typedef union 
{
    UINT8           Byte[2];
    UINT16          Word;
}
UALEVENT;

/*-----------------------------------------------------------------------------------------
------
------    Global Interrupt setting
------
-----------------------------------------------------------------------------------------*/
#define DISABLE_GLOBAL_INT      __disable_irq()
#define ENABLE_GLOBAL_INT       __enable_irq()

#define DISABLE_AL_EVENT_INT    DISABLE_GLOBAL_INT
#define ENABLE_AL_EVENT_INT     ENABLE_GLOBAL_INT

/*-----------------------------------------------------------------------------------------
------
------    ESC Interrupt
------
-----------------------------------------------------------------------------------------*/

#define     INIT_ESC_INT            Exit_IRQ_Init();
#define     EcatIsr                 EXTI3_IRQHandler
#define     ACK_ESC_INT             HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3)
#define     IS_ESC_INT_ACTIVE       (rt_pin_read(GET_PIN(E,3)) == 0)
/*-----------------------------------------------------------------------------------------
------
------    SYNC0 Interrupt
------
-----------------------------------------------------------------------------------------*/
#define INIT_SYNC0_INT          Exit_SYNC0_Init()
#define Sync0Isr                EXTI4_IRQHandler
#define DISABLE_SYNC0_INT       HAL_NVIC_DisableIRQ(EXTI4_IRQn)
#define ENABLE_SYNC0_INT        HAL_NVIC_EnableIRQ(EXTI4_IRQn)
#define ACK_SYNC0_INT           HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4)
#define IS_SYNC0_INT_ACTIVE     ((GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)) == 0)

/*ECATCHANGE_START(V5.10) HW3*/
#define INIT_SYNC1_INT          Exit_SYNC1_Init()
#define Sync1Isr                EXTI9_5_IRQHandler
#define DISABLE_SYNC1_INT       HAL_NVIC_DisableIRQ(EXTI9_5_IRQn);
#define ENABLE_SYNC1_INT        HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
#define ACK_SYNC1_INT           HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
#define IS_SYNC1_INT_ACTIVE     ((GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_5)) == 0)

/*-----------------------------------------------------------------------------------------
------
------    Hardware timer
------
-----------------------------------------------------------------------------------------*/

#if ECAT_TIMER_INT
    #define ECAT_TIMER_ACK_INT
    #define TimerIsr                        SysTick_Handler
    #define INIT_ECAT_TIMER                 rt_hw_systick_init();
#else    //#if ECAT_TIMER_INT
    #define TimerIsr                        TIM2_IRQHandler
    #define INIT_ECAT_TIMER                 MX_TIM2_Init();
#endif //#else #if ECAT_TIMER_INT


/*--------------------------------------------------------------------------------------
------
------    internal Variables
------
--------------------------------------------------------------------------------------*/
UALEVENT         EscALEvent;            //contains the content of the ALEvent register (0x220), this variable is updated on each Access to the Esc

/*--------------------------------------------------------------------------------------
------
------    internal functions
------
--------------------------------------------------------------------------------------*/
/*******************************************************************************
  Description:
    The first two bytes of an access to the EtherCAT ASIC always deliver the AL_Event register (0x220).
    It will be saved in the global "EscALEvent"
  *****************************************************************************/
static void GetInterruptRegister(void)
{
    DISABLE_AL_EVENT_INT;
    HW_EscReadIsr((MEM_ADDR *)&EscALEvent.Word, 0x220, 2);
    ENABLE_AL_EVENT_INT;
}

/*******************************************************************************
  Description:
    The first two bytes of an access to the EtherCAT ASIC always deliver the AL_Event register (0x220).
        It will be saved in the global "EscALEvent"
  *****************************************************************************/
static void ISR_GetInterruptRegister(void)
{
    HW_EscReadIsr((MEM_ADDR *)&EscALEvent.Word, 0x220, 2);
}

/*--------------------------------------------------------------------------------------
------
------    exported hardware access functions
------
--------------------------------------------------------------------------------------*/
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \return     0 if initialization was successful
 \brief    This function intialize the Process Data Interface (PDI) and the host controller.
*////////////////////////////////////////////////////////////////////////////////////////
uint8_t HW_Init(void)
{
    uint32_t intMask;
    DISABLE_GLOBAL_INT;

    MPU_Config();
    MX_FSMC_Init();

    do
    {
        intMask = PMPReadDWord(0x0064);
    } while (intMask!= 0x87654321);

    do
    {
        intMask = PMPReadDWord(0x0050);
    } while (intMask!= 0x92520001);

    do
    {
        intMask = 0x0093;
        HW_EscWriteDWord(intMask, ESC_AL_EVENTMASK_OFFSET);
        intMask = 0;
        HW_EscReadDWord(intMask, ESC_AL_EVENTMASK_OFFSET);
    } while (intMask!= 0x0093);

    //IRQ enable,IRQ polarity, IRQ buffer type in Interrupt Configuration register.
    //Wrte 0x54 - 0x00000101
    PMPWriteDWord (0x54,0x00000101);


    //Write in Interrupt Enable register -->
    //Write 0x5c - 0x00000001
    PMPWriteDWord (0x5C, 0x00000001);
    PMPReadDWord(0x58);

    intMask = 0x00;

    HW_EscWriteDWord(intMask, ESC_AL_EVENTMASK_OFFSET);


#if AL_EVENT_ENABLED
    INIT_ESC_INT;
    ENABLE_ESC_INT();
#endif

#if DC_SUPPORTED
    INIT_SYNC0_INT;
    INIT_SYNC1_INT;

    ENABLE_SYNC0_INT;
    ENABLE_SYNC1_INT;
#endif

    INIT_ECAT_TIMER;


#if INTERRUPTS_SUPPORTED
    /* enable all interrupts */
    ENABLE_GLOBAL_INT;
#endif
    return 0;
}

/////////////////////////////////////////////////////////////////////////////////////////
/**
 \brief    This function shall be implemented if hardware resources need to be release
        when the sample application stops
*////////////////////////////////////////////////////////////////////////////////////////
void HW_Release(void)
{

}

/////////////////////////////////////////////////////////////////////////////////////////
/**
 \return    first two Bytes of ALEvent register (0x220)

 \brief  This function gets the current content of ALEvent register
*////////////////////////////////////////////////////////////////////////////////////////
UINT16 HW_GetALEventRegister(void)
{
    GetInterruptRegister();
    return EscALEvent.Word;
}
#if INTERRUPTS_SUPPORTED
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \return    first two Bytes of ALEvent register (0x220)

 \brief  The SPI PDI requires an extra ESC read access functions from interrupts service routines.
        The behaviour is equal to "HW_GetALEventRegister()"
*////////////////////////////////////////////////////////////////////////////////////////
UINT16 HW_GetALEventRegister_Isr(void)
{
    ISR_GetInterruptRegister();
    return EscALEvent.Word;
}
#endif

/////////////////////////////////////////////////////////////////////////////////////////
/**
 \param pData        Pointer to a byte array which holds data to write or saves read data.
 \param Address     EtherCAT ASIC address ( upper limit is 0x1FFF )    for access.
 \param Len            Access size in Bytes.

 \brief  This function operates the SPI read access to the EtherCAT ASIC.
*////////////////////////////////////////////////////////////////////////////////////////
void HW_EscRead( MEM_ADDR *pData, UINT16 Address, UINT16 Len )
{
    UINT16 i;
    UINT8 *pTmpData = (UINT8 *)pData;

    /* loop for all bytes to be read */
    while ( Len > 0 )
    {
        if (Address >= 0x1000)
        {
            i = Len;
        }
        else
        {
            i= (Len > 4) ? 4 : Len;
        }

        DISABLE_AL_EVENT_INT;
        PMPReadDRegister(pTmpData,Address,i);
        ENABLE_AL_EVENT_INT;

        Len -= i;
        pTmpData += i;
        Address += i;
    }
}
#if INTERRUPTS_SUPPORTED
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \param pData        Pointer to a byte array which holds data to write or saves read data.
 \param Address     EtherCAT ASIC address ( upper limit is 0x1FFF )    for access.
 \param Len            Access size in Bytes.

\brief  The SPI PDI requires an extra ESC read access functions from interrupts service routines.
        The behaviour is equal to "HW_EscRead()"
*////////////////////////////////////////////////////////////////////////////////////////
void HW_EscReadIsr( MEM_ADDR *pData, UINT16 Address, UINT16 Len )
{
   UINT16 i;
   UINT8 *pTmpData = (UINT8 *)pData;
    /* loop for all bytes to be read */
   while ( Len > 0 )
   {
        if (Address >= 0x1000)
        {
            i = Len;
        }
        else
        {
            i= (Len > 4) ? 4 : Len;
        }

        PMPReadDRegister(pTmpData, Address,i);

        Len -= i;
        pTmpData += i;
        Address += i;
    }
}
#endif //#if INTERRUPTS_SUPPORTED
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \param pData        Pointer to a byte array which holds data to write or saves write data.
 \param Address     EtherCAT ASIC address ( upper limit is 0x1FFF )    for access.
 \param Len            Access size in Bytes.

  \brief  This function operates the SPI write access to the EtherCAT ASIC.
*////////////////////////////////////////////////////////////////////////////////////////
void HW_EscWrite( MEM_ADDR *pData, UINT16 Address, UINT16 Len )
{
    UINT16 i;
    UINT8 *pTmpData = (UINT8 *)pData;

    /* loop for all bytes to be written */
    while ( Len )
    {
        if (Address >= 0x1000)
        {
            i = Len;
        }
        else
        {
            i= (Len > 4) ? 4 : Len;

            if(Address & 01)
            {
               i=1;
            }
            else if (Address & 02)
            {
               i= (i&1) ? 1:2;
            }
            else if (i == 03)
            {
                i=1;
            }
        }
        DISABLE_AL_EVENT_INT;
        /* start transmission */
        PMPWriteRegister(pTmpData, Address, i);
        ENABLE_AL_EVENT_INT;
        /* next address */
        Len -= i;
        pTmpData += i;
        Address += i;
    }
}
#if INTERRUPTS_SUPPORTED
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \param pData        Pointer to a byte array which holds data to write or saves write data.
 \param Address     EtherCAT ASIC address ( upper limit is 0x1FFF )    for access.
 \param Len            Access size in Bytes.

 \brief  The SPI PDI requires an extra ESC write access functions from interrupts service routines.
        The behaviour is equal to "HW_EscWrite()"
*////////////////////////////////////////////////////////////////////////////////////////
void HW_EscWriteIsr( MEM_ADDR *pData, UINT16 Address, UINT16 Len )
{
    UINT16 i ;
    UINT8 *pTmpData = (UINT8 *)pData;
    /* loop for all bytes to be written */
    while ( Len )
    {
        if (Address >= 0x1000)
        {
            i = Len;
        }
        else
        {
            i= (Len > 4) ? 4 : Len;

            if(Address & 01)
            {
               i=1;
            }
            else if (Address & 02)
            {
               i= (i&1) ? 1:2;
            }
            else if (i == 03)
            {
                i=1;
            }
        }
       /* start transmission */
       PMPWriteRegister(pTmpData, Address, i);
       /* next address */
        Len -= i;
        pTmpData += i;
        Address += i;
    }
}
/*******************************************************************************
  Function:
    void __attribute__ ((__interrupt__, no_auto_psv)) EscIsr(void)

  Summary:
    Interrupt service routine for the PDI interrupt from the EtherCAT Slave Controller

  *****************************************************************************/

void  EcatIsr(void)
{
    /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */
    rt_interrupt_enter();

    PDI_Isr();
    ACK_ESC_INT;

    rt_interrupt_leave();
    /* USER CODE END TIM1_UP_TIM10_IRQn 1 */
}
#endif     // AL_EVENT_ENABLED

#if DC_SUPPORTED
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \brief    Interrupt service routine for the interrupts from SYNC0
*////////////////////////////////////////////////////////////////////////////////////////

void Sync0Isr(void)
{
    /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */
    rt_interrupt_enter();

    DISABLE_ESC_INT();
    Sync0_Isr();
    /* reset the interrupt flag */
    ACK_SYNC0_INT;
    ENABLE_ESC_INT();

    rt_interrupt_leave();
    /* USER CODE END TIM1_UP_TIM10_IRQn 1 */
}
/*ECATCHANGE_START(V5.10) HW3*/
/////////////////////////////////////////////////////////////////////////////////////////
/**
 \brief    Interrupt service routine for the interrupts from SYNC1
*////////////////////////////////////////////////////////////////////////////////////////

void Sync1Isr(void)
{
    /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */
    rt_interrupt_enter();

    DISABLE_ESC_INT();
    Sync1_Isr();
    ACK_SYNC1_INT;
    ENABLE_ESC_INT();

    rt_interrupt_leave();
    /* USER CODE END TIM1_UP_TIM10_IRQn 1 */
}
/*ECATCHANGE_END(V5.10) HW3*/
#endif

#if !ECAT_TIMER_INT
// Timer 2 ISR (1ms)
void TimerIsr(void)
{

    /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */
    rt_interrupt_enter();

    ECAT_CheckTimer();
    /* USER CODE END TIM1_UP_TIM10_IRQn 0 */
    HAL_TIM_IRQHandler(&htim2);
    /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */
    rt_interrupt_leave();
    /* USER CODE END TIM1_UP_TIM10_IRQn 1 */
#if ECAT_TIMER_INT
    ECAT_TIMER_ACK_INT;
#endif
}

#endif
